<?php

namespace fakis\core\web;

use packages\a1\App;
use Yii;
use yii\base\Application;
use yii\base\Event;
use yii\base\InvalidValueException;
use yii\helpers\ArrayHelper;

/**
 * 应用管理器
 *
 * @property AppModule|null $app 当前应用模块
 * @property array $appModules 所有应用模块配置
 * @property array $urlRules 所有应用模块的路由配置
 *
 * @property-read bool $isAppModule 当前是否调用应用模块
 * @property-read string[] $appModuleIds 取得所有应用模块ID
 * @property-read string[] $srcModuleIds 取得所有原生模块ID
 * @property-read string[] $allModuleIds 取得所有模块ID
 *
 * @author Fakis <fakis738@qq.com>
 */
class AppManager extends \yii\base\Component implements \yii\base\BootstrapInterface
{
    /**
     * @var string
     */
    public $appModelClass;

    /**
     * @var AppModule|null
     */
    private $_app;

    /**
     * 应用模块配置
     * @var array
     */
    private $_appModules = [];

    /**
     * 应用模块的路由配置
     * @var array
     */
    private $_urlRules = [];

    /**
     * 返回当前应用模块ID
     * @return string|null
     */
    public function getId()
    {
        return ($app = $this->getApp()) !== null ? $app->id : null;
    }

    /**
     * 返回当前应用模块
     * @return AppModule|null
     */
    public function getApp()
    {
        return $this->_app;
    }

    /**
     * 设置当前应用模块
     * @param AppModule|string|null $appModule
     */
    public function setApp($appModule)
    {
        if ($appModule instanceof AppModule) {
            $this->_app = $appModule;
            $this->_app->instance();
        } elseif (is_string($appModule) && $this->has($appModule)) {
            $this->_app = $this->get($appModule);
            $this->_app->instance();
        } elseif ($appModule === null) {
            $this->_app = null;
        } else {
            throw new InvalidValueException('设置当前应用模块无效');
        }
    }

    /**
     * 返回当前是否调用应用模块
     * @return bool
     */
    public function getIsAppModule()
    {
        return $this->getId() !== null;
    }

    /**
     * 根据当前请求的应用模块，如果应用模块不存在，则返回原生应用
     * @return AppModule|Application
     */
    public function app()
    {
        return $this->getApp() ?? Yii::$app;
    }

    /**
     * 返回应用模块
     * @param string $id 应用ID，如果为空，则为当前请求应用
     * @return AppModule|null
     */
    public function get($id)
    {
        if ($this->has($id)) {
            $app = Yii::$app->getModule($id);
            return $app instanceof AppModule ? $app : null;
        }
        return null;
    }

    /**
     * 返回应用模块是否存在
     * @param string $id
     * @return bool
     */
    public function has($id)
    {
        return in_array($id, $this->getAppModuleIds(), true);
    }

    /**
     * 取得默认应用模块配置
     * @return array
     */
    public function getDefaultAppModules()
    {
        return [
            'a1' => [
                'class' => App::class,
            ],
        ];
    }

    /**
     * 取得所有应用模块配置
     * @return array
     */
    public function getAppModules()
    {
        return ArrayHelper::merge($this->getDefaultAppModules(), $this->_appModules);
    }

    /**
     * 配置应用模块
     * @param array $appModules
     */
    public function setAppModules($appModules)
    {
        foreach ($appModules as $id => $appModule) {
            $this->_appModules[$id] = $appModule;
        }
    }

    /**
     * 取得默认应用模块的路由规则配置
     * @return array
     */
    public function getDefaultUrlRules()
    {
        return [];
    }

    /**
     * 取得所有应用模块的路由规则配置
     * @return array
     */
    public function getUrlRules()
    {
        return ArrayHelper::merge($this->getDefaultUrlRules(), $this->_urlRules);
    }

    /**
     * 配置应用模块的路由规则
     * @param array $rules
     */
    public function setUrlRules($rules)
    {
        $this->_urlRules = $rules;
    }

    /**
     * 返回所有应用模块ID
     * @return string[]
     */
    public function getAppModuleIds()
    {
        return array_keys($this->getAppModules());
    }

    /**
     * 返回所有原生模块ID
     * @return string[]
     */
    public function getSrcModuleIds()
    {
        return array_keys(array_diff_key(Yii::$app->getModules(), $this->getAppModules()));
    }

    /**
     * 返回所有模块ID
     * @return string[]
     */
    public function getAllModuleIds()
    {
        return array_keys(Yii::$app->getModules());
    }

    /**
     * Bootstrap method to be called during application bootstrap stage.
     * @param Application
     */
    public function bootstrap($app)
    {
        // 配置所有应用模块
        $this->bootstrapAppModules($app);

        // 载入所有应用路由规则
        $this->bootstrapUrlRules($app);

        // 挂载原生应用的请求前事件
        $app->on(Application::EVENT_BEFORE_REQUEST, [$this, 'beforeRequest']);
    }

    /**
     * 配置所有应用模块
     * @param Application $app
     */
    protected function bootstrapAppModules($app)
    {
        $app->setModules($this->getAppModules());
    }

    /**
     * 配置所有应用模块的路由规则
     * @param Application $app
     */
    protected function bootstrapUrlRules($app)
    {
        $app->getUrlManager()->addRules($this->getUrlRules());
    }

    /**
     * 通过解析路由捕获当前请求的应用模块
     * @param Event $event
     */
    protected function beforeRequest(Event $event)
    {
        /** @var \yii\web\Application $app */
        $app = $event->sender;

        list($route, $params) = $app->getUrlManager()->parseRequest($app->getRequest());
        if (
            $route !== ''
            && ($part = explode('/', $route)[0])
            && $this->has($part)
        ) {
            $this->setApp($part);
        }
    }

    /**
     * 生成应用ID
     * @return string
     */
    public function generateId(string $str = '')
    {
        $hex = static::class . PHP_OS . PHP_SAPI . $str . microtime();
        $id = sprintf("%'.08x", crc32($hex));

        return in_array($id, $this->getAllModuleIds(), true)
            ? $this->generateId($str)
            : $id;
    }
}